﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using QuickTuner.Util;
using QuickTuner.Properties;

namespace QuickTuner
{
    public partial class radioForm : Form
    {
        // class to help store validity of states across multiple threads that updates the UI
        protected class ValidAsyncState
        {
            public ValidAsyncState() { IsStillValid = true; }
            public bool IsStillValid { get; set; }
        }

        private Radio _radio;                           // the Radio engine

        private List<ToolStripMenuItem> _menuItems;     // List of items for later ref.

        private ValidAsyncState _lastState;             // Track background UI thread validity

        private bool _isDragging;                       // Track form dragging
        private Point _lastMouseLocation;               // Track mouse for form-dragging

        private double _targetOpactiy;                  // Opacity level tracking for animation
        private bool _suppressAnimation;                // Flag for supressing animation

        // animation constants
        private const double OpactiyAnimationFactor = 0.2;
        private const double OpacityAnimationThreshold = 0.05;
        private const double ActiveOpacity = 1.0;
        private const double InactiveOpacity = 0.5;

        // constructors _________________________________________________________________

        public radioForm(Radio theRadio)
        {
            InitializeComponent();

            this.Icon = Resources.appIcon;
            trayIcon.Icon = Resources.appIcon;


            // attach to the radio engine
            _radio = theRadio;
            _radio.CurrentStationChanged += radio_CurrentStationChanged;
            _radio.PlayStateChanged += radio_PlayStateChnged;

            // bogus state to avoid null checks
            _lastState = new ValidAsyncState();

            // menu items cache
            _menuItems = new List<ToolStripMenuItem>();

            // update UI to match current radio state
            updatePlayState();
            updateCurrentStation();
            updateStationList();


            // setup animation parameters

            // it's likely that the mouse is not directly on the form
            // when the form starts, so we open the form with inactive mode
            this.Opacity = _targetOpactiy = InactiveOpacity;

        }

        // UI update routines ___________________________________________________________

        private void updateStationList()
        {
            // remove all cached items
            _menuItems.ForEach(item => stationsListMenu.Items.Remove(item));
            _menuItems.Clear();

            // rebuild menu items
            _menuItems.AddRange(_radio.Stations.Select(station => {

                var menuItem = new ToolStripMenuItem();
                menuItem.Text = station.Title;

                // download icon in the background
                IconCache.BeginGetIcon(station, ar => {
                    var img = IconCache.EndGetIcon(ar);
                    if (img != null) setToolStripImage(menuItem, img);
                });

                menuItem.Tag = station;

                menuItem.Click += stationItem_Click;
                return menuItem;
            }));

            // add them back to the drop down
            // index is required so items are inserted above any existing menus there
            var index = 0;
            _menuItems.ForEach(item => stationsListMenu.Items.Insert(index++, item));
        }

        private void updateCurrentStation()
        {
            var station = _radio.CurrentStation;

            stationsListDropDown.Text = station.Title;

            // invalid last session
            _lastState.IsStillValid = false;

            // create a new state flag
            _lastState = new ValidAsyncState();

            IconCache.BeginGetIcon(station, ar => {
                var img = IconCache.EndGetIcon(ar) ?? Resources.musicImage;

                // check if this download operation is still valid
                // if so, we assign the image normally, else we just return.
                if ((ar.AsyncState as ValidAsyncState).IsStillValid)
                    setToolStripImage(stationsListDropDown, img);

            }, _lastState);
        }

        private void updatePlayState()
        {
            if (_radio.IsPlaying) {
                playToggleButton.Image = Resources.stopImage;
                playToggleMenu.Image = Resources.stopImage;
                playToggleMenu.Text = "Stop";

            } else {
                playToggleButton.Image = Resources.playImage;
                playToggleMenu.Image = Resources.playImage;
                playToggleMenu.Text = "Play";

            }
        }

        // cross-thread helper
        private void setToolStripImage(ToolStripItem item, Image img)
        {
            if (this.InvokeRequired)
                this.Invoke(new Action<ToolStripItem, Image>(setToolStripImage),
                    item, img);

            else {
                item.Image = img;

            }
        }

        // Event Handlers _______________________________________________________________

        // Radio Events

        private void radio_CurrentStationChanged(object sender, EventArgs e)
        {
            updateCurrentStation();
        }

        private void radio_PlayStateChnged(object sender, EventArgs e)
        {
            updatePlayState();
        }

        // UI Events

        private void stationItem_Click(object sender, EventArgs e)
        {
            var control = (sender as ToolStripMenuItem);

            var station = control.Tag as Station;
            _radio.CurrentStation = station;
        }

        private void quitButton_Click(object sender, EventArgs e)
        {
            _radio.Stop();
            this.Close();
        }

        private void quitButton_MouseEnter(object sender, EventArgs e)
        {
            // change into red image
            var target = (ToolStripItem)sender;
            target.Image = Resources.quitImage;
        }
        private void quitButton_MouseLeave(object sender, EventArgs e)
        {
            // change into grayed image
            var target = (ToolStripItem)sender;
            target.Image = Resources.quitGrayImage;
        }

        private void playToggleButton_Click(object sender, EventArgs e)
        {
            // issue proper play/stop
            if (!_radio.IsPlaying)
                _radio.Play();
            else
                _radio.Stop();

            updatePlayState();
        }

        private void editStationsMenu_Click(object sender, EventArgs e)
        {
            if (!editForm.InstanceRunning) {
                new editForm(_radio.Stations).ShowDialog();
                updateStationList();
            }
        }

        // dragging support

        private void dragHolder_MouseDown(object sender, MouseEventArgs e)
        {
            _lastMouseLocation = e.Location;
            _isDragging = true;
        }
        private void dragHolder_MouseMove(object sender, MouseEventArgs e)
        {
            if (_isDragging)
                calculateDrag(e.Location);
        }
        private void dragHolder_MouseLeave(object sender, EventArgs e)
        {
            if (_isDragging)
                calculateDrag(toolStrip.PointToClient(Control.MousePosition));
        }
        private void dragHolder_MouseUp(object sender, MouseEventArgs e)
        {
            _isDragging = false;
        }

        private void calculateDrag(Point newMouseLocation)
        {
            var dx = newMouseLocation.X - _lastMouseLocation.X;
            var dy = newMouseLocation.Y - _lastMouseLocation.Y;

            this.Location = new Point(
                this.Location.X + dx,
                this.Location.Y + dy);
        }

        // animation events

        private void animationTimer_Tick(object sender, EventArgs e)
        {
            if (this.Opacity != _targetOpactiy) {
                var delta = _targetOpactiy - this.Opacity;

                this.Opacity += Math.Abs(delta) < OpacityAnimationThreshold
                    ? delta
                    : delta * OpactiyAnimationFactor;

            } else {
                animationTimer.Stop();

            }
        }

        private void radioForm_MouseEnter(object sender, EventArgs e)
        {
            _targetOpactiy = ActiveOpacity;
            if (!_suppressAnimation) animationTimer.Start();
        }

        private void radioForm_MouseLeave(object sender, EventArgs e)
        {
            _targetOpactiy = InactiveOpacity;
            if (!_suppressAnimation) animationTimer.Start();
        }

        // workaround toolStrip not reporting mouse enter/leave to form first
        private void toolStrip_MouseLeave(object sender, EventArgs e)
        {
            this.OnMouseLeave(e);
        }
        private void toolStrip_MouseEnter(object sender, EventArgs e)
        {
            this.OnMouseEnter(e);
        }

        // when user move mouse over to select item on dropdownlist, the cursor
        // leaves the toolStrip triggering inactive animation which is not desired
        // since user is still using the form thus we need the suppress flag
        private void stationsListDropDown_DropDownOpened(object sender, EventArgs e)
        {
            _suppressAnimation = true;
        }
        private void stationsListDropDown_DropDownClosed(object sender, EventArgs e)
        {
            _suppressAnimation = false;

            // check if we should re-fire MouseLeave event
            // after releasing animation suppression.
            if (!this.Bounds.Contains(Control.MousePosition))
                OnMouseLeave(e);
        }

        private void trayIcon_MouseClick(object sender, MouseEventArgs e)
        {
            // show/hide the form on single-left-click
            if (e.Button == MouseButtons.Left)
                this.Visible = !this.Visible;
        }

    }


}
